import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:fl_clash/clash/interface.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/core.dart';

class ClashService extends ClashHandlerInterface {
  static ClashService? _instance;

  Completer<ServerSocket> serverCompleter = Completer();

  Completer<Socket> socketCompleter = Completer();

  Process? process;

  factory ClashService() {
    _instance ??= ClashService._internal();
    return _instance!;
  }

  ClashService._internal() {
    _initServer();
    reStart();
  }

  _initServer() async {
    final address = !Platform.isWindows
        ? InternetAddress(
            unixSocketPath,
            type: InternetAddressType.unix,
          )
        : InternetAddress(
            localhost,
            type: InternetAddressType.IPv4,
          );
    await _deleteSocketFile();
    final server = await ServerSocket.bind(
      address,
      0,
      shared: true,
    );
    serverCompleter.complete(server);
    await for (final socket in server) {
      await _destroySocket();
      socketCompleter.complete(socket);
      socket
          .transform(
            StreamTransformer<Uint8List, String>.fromHandlers(
              handleData: (Uint8List data, EventSink<String> sink) {
                sink.add(utf8.decode(data, allowMalformed: true));
              },
            ),
          )
          .transform(LineSplitter())
          .listen(
            (data) {
              handleResult(
                ActionResult.fromJson(
                  json.decode(data.trim()),
                ),
              );
            },
          );
    }
  }

  @override
  reStart() async {
    if (process != null) {
      await shutdown();
    }
    final serverSocket = await serverCompleter.future;
    final arg = Platform.isWindows
        ? "${serverSocket.port}"
        : serverSocket.address.address;
    bool isSuccess = false;
    if (Platform.isWindows && await system.checkIsAdmin()) {
      isSuccess = await request.startCoreByHelper(arg);
    }
    if (isSuccess) {
      return;
    }
    process = await Process.start(
      appPath.corePath,
      [
        arg,
      ],
    );
    process!.stdout.listen((_) {});
  }

  @override
  destroy() async {
    final server = await serverCompleter.future;
    await server.close();
    await _deleteSocketFile();
    return true;
  }

  @override
  sendMessage(String message) async {
    final socket = await socketCompleter.future;
    socket.writeln(message);
  }

  _deleteSocketFile() async {
    if (!Platform.isWindows) {
      final file = File(unixSocketPath);
      if (await file.exists()) {
        await file.delete();
      }
    }
  }

  _destroySocket() async {
    if (socketCompleter.isCompleted) {
      final lastSocket = await socketCompleter.future;
      await lastSocket.close();
      socketCompleter = Completer();
    }
  }

  @override
  shutdown() async {
    await super.shutdown();
    if (Platform.isWindows) {
      await request.stopCoreByHelper();
    }
    await _destroySocket();
    process?.kill();
    process = null;
    return true;
  }

  @override
  Future<bool> preload() async {
    await serverCompleter.future;
    return true;
  }
}

final clashService = system.isDesktop ? ClashService() : null;
